#include "hardware/flash.h"
#include "hardware/sync.h"
#include "lfs.h"
#include "log.h"
#include "arduino.h"
#include "httpd_fs.h"
#include "httpd_cgi.h"

#define FLASH_BASE              0x10000000
#define FLASH_SIZE_ALL          0x1000000
#define FLASH_SIZE_OFFSET       0x400000
#define FLASH_SIZE_USED         (FLASH_SIZE_ALL - FLASH_SIZE_OFFSET)
#define FLASH_SIZE_ERASE        4096
#define FLASH_SIZE_SECTOR       256

// variables used by the filesystem
static lfs_t g_lfs_fs;
static const struct lfs_config g_lfs_cfg;
static char *g_lfs_current;

static int user_provided_block_device_read(const struct lfs_config *c, lfs_block_t block,
            lfs_off_t off, void *buffer, lfs_size_t size)
{
    memcpy(buffer, (void *)(FLASH_BASE + FLASH_SIZE_OFFSET + block * g_lfs_cfg.block_size + off), size);
    // spiflash_read(block * g_lfs_cfg.block_size + off, buffer, size);
    return 0;
}

static int user_provided_block_device_prog(const struct lfs_config *c, lfs_block_t block,
            lfs_off_t off, const void *buffer, lfs_size_t size)
{
    uint32_t save = save_and_disable_interrupts();

    flash_range_program(FLASH_SIZE_OFFSET + block * g_lfs_cfg.block_size + off, buffer, size);
    // spiflash_write(block * g_lfs_cfg.block_size + off, buffer, size);
    if ((off & (FLASH_SIZE_SECTOR - 1)) || (size & (FLASH_SIZE_SECTOR - 1))) {
        log_printf("error: write block %d offset %d\n", block, off);
    }
    restore_interrupts(save);
    return 0;
}

static int user_provided_block_device_erase(const struct lfs_config *c, lfs_block_t block)
{
    uint32_t save = save_and_disable_interrupts();

    flash_range_erase(FLASH_SIZE_OFFSET + block * g_lfs_cfg.block_size, g_lfs_cfg.block_size);
    // spiflash_erase(block * g_lfs_cfg.block_size);
    restore_interrupts(save);
    return 0;
}

static int user_provided_block_device_sync(const struct lfs_config *c)
{
    return 0;
}

// configuration of the filesystem is provided by this struct
static const struct lfs_config g_lfs_cfg = {
    // block device operations
    .read  = user_provided_block_device_read,
    .prog  = user_provided_block_device_prog,
    .erase = user_provided_block_device_erase,
    .sync  = user_provided_block_device_sync,

    // block device configuration
    .read_size = 16,
    .prog_size = FLASH_SIZE_SECTOR,
    .block_size = FLASH_SIZE_ERASE,
    .block_count = FLASH_SIZE_USED / FLASH_SIZE_ERASE,
    .cache_size = FLASH_SIZE_SECTOR,
    .lookahead_size = 16,
    .block_cycles = 500,
};

// entry point
void lfs_rp2040_init(void)
{
    // lfs_file_t file;

    // spiflash_init();
    
    // mount the filesystem
    int err = lfs_mount(&g_lfs_fs, &g_lfs_cfg);

    // reformat if we can't mount the filesystem
    // this should only happen on the first boot
    if (err) {
        lfs_format(&g_lfs_fs, &g_lfs_cfg);
        err = lfs_mount(&g_lfs_fs, &g_lfs_cfg);
        if (err) {
            log_printf("Failed to mount filesystem.\n");
            return;
        }
    }
    log_printf("Mount filesystem succ.\n");
}

static int lfs_httpd_open(struct fs_file *file, const char *name, void **priv)
{
    lfs_file_t *fp = (lfs_file_t *)malloc(sizeof(lfs_file_t));

    file->data = NULL;    
    if (!fp) {
        return 0;
    }
    if (lfs_file_open(&g_lfs_fs, fp, name, LFS_O_RDONLY) < 0) {
        free(fp);
        return 0;
    }
    file->len = 128;
    *priv = fp;
    return 1;
}

static int lfs_httpd_read(struct fs_file *file, char *buffer, int count, void **priv)
{
    lfs_ssize_t size = lfs_file_read(&g_lfs_fs, (lfs_file_t *)(*priv), buffer, count);
    if (size <= 0) {
        return FS_READ_EOF;
    }
    return size;
}

static int lfs_httpd_write(struct fs_file *file, const char *buffer, int count, void **priv)
{
    return 0;
}

static void lfs_httpd_close(struct fs_file *file, void **priv)
{
    if (!priv || !*priv) {
        return;
    }
    lfs_file_close(&g_lfs_fs, (lfs_file_t *)(*priv));
    free(*priv);
}

HTTPD_FILE_DEFINE(NULL, lfs_httpd_open, lfs_httpd_read, lfs_httpd_write, lfs_httpd_close);

static int fs_open_files_json(struct fs_file *file, const char *name, void **priv)
{
    char buffer[2048] = {0};
    lfs_dir_t dir = {0};
    struct lfs_info info = {0};
    int cnt = 0;
    char *pos = buffer;
    char *start = "/";

    if (g_lfs_current) {
        start = g_lfs_current;
    }
    if (lfs_dir_open(&g_lfs_fs, &dir, start) == 0) {
        pos += snprintf(pos, buffer + sizeof(buffer) - pos - 1, "{\"files\":[\n");
        while (1) {
            if (lfs_dir_read(&g_lfs_fs, &dir, &info) <= 0) {
                break;
            }
            if (cnt != 0) {
                pos += snprintf(pos, buffer + sizeof(buffer) - pos - 1, ",\n");
            }
            cnt++;
            if (info.type == LFS_TYPE_DIR) {
                pos += snprintf(pos, buffer + sizeof(buffer) - pos - 1, "{\"type\":\"dir\",\"name\":\"%s\"}", info.name);
            } else {
                pos += snprintf(pos, buffer + sizeof(buffer) - pos - 1, "{\"type\":\"file\",\"name\":\"%s\", \"size\":%d}", info.name, (int)info.size);
            }
        }
        pos += snprintf(pos, buffer + sizeof(buffer) - pos - 1, "]}\n");
        lfs_dir_close(&g_lfs_fs, &dir);
    }

    file->len = pos - buffer;
    file->index = file->len;
    file->data = malloc(file->len+ 1);
    file->flags = 0;
    strcpy((char *)file->data, buffer);
    return 1;
}

HTTPD_FILE_DEFINE("/files.json", fs_open_files_json, NULL, NULL, NULL);

static void lfs_httpd_mkdir_ssi(char *value)
{
    char buffer[128] = {0};

    if (value == NULL || value[0] == 0){
        return;
    }
    if (g_lfs_current) {
        strcpy(buffer, g_lfs_current);
        strcat(buffer, "/");
    }
    strcat(buffer, value);
    lfs_mkdir(&g_lfs_fs, buffer);
}
HTTPD_CGI_PARAM_DEFINE("mkdir", lfs_httpd_mkdir_ssi);

static void lfs_httpd_rm_ssi(char *value)
{
    char buffer[128] = {0};

    if (g_lfs_current) {
        strcpy(buffer, g_lfs_current);
        strcat(buffer, "/");
    }
    strcat(buffer, value);
    lfs_remove(&g_lfs_fs, buffer);
}
HTTPD_CGI_PARAM_DEFINE("rm", lfs_httpd_rm_ssi);

static void lfs_httpd_touch_ssi(char *value)
{
    lfs_file_t file;
    char buffer[128] = {0};

    if (value == NULL || value[0] == 0){
        return;
    }
    if (g_lfs_current) {
        strcpy(buffer, g_lfs_current);
        strcat(buffer, "/");
    }
    strcat(buffer, value);
    lfs_file_open(&g_lfs_fs, &file, buffer, LFS_O_CREAT);
    lfs_file_close(&g_lfs_fs, &file);
}
HTTPD_CGI_PARAM_DEFINE("touch", lfs_httpd_touch_ssi);

static void lfs_httpd_mkfs_ssi(char *value)
{
    lfs_format(&g_lfs_fs, &g_lfs_cfg);
    lfs_mount(&g_lfs_fs, &g_lfs_cfg);
}
HTTPD_CGI_PARAM_DEFINE("mkfs", lfs_httpd_mkfs_ssi);

static void lfs_httpd_cd_ssi(char *value)
{
    if (value == NULL || value[0] == 0){
        return;
    }
    if (strcmp(value, ".") == 0) {
        return;
    }
    if (strcmp(value, "..") == 0) {
        if (!g_lfs_current) {
            return;
        }
        int pos = strlen(g_lfs_current) - 1;
        while (pos > 0 && g_lfs_current[pos] != '/') {
            pos--;
        }
        if (pos > 0) {
            g_lfs_current[pos] = 0;
        }
        if (pos == 0) {
            strcpy(g_lfs_current, "/");
        }
        return;
    }
    if (g_lfs_current) {
        int len = strlen(g_lfs_current) + strlen(value) + 10;
        char *new_path = malloc(len);

        strcpy(new_path, g_lfs_current);
        free(g_lfs_current);
        g_lfs_current = new_path;
        int pos = strlen(g_lfs_current) - 1;
        if ((pos < 0) || (g_lfs_current[pos] != '/')) {
            strcat(g_lfs_current, "/");
        }
        strcat(g_lfs_current, value);
    } else {
        int len = strlen(value) + 10;
        g_lfs_current = malloc(len);
        strcpy(g_lfs_current, value);
    }
}
HTTPD_CGI_PARAM_DEFINE("cd", lfs_httpd_cd_ssi);

lfs_file_t *lfs_spiflash_open(const char *path, int flags, int abs)
{
    char buffer[256] = {0};

    if (g_lfs_current && !abs) {
        strcpy(buffer, g_lfs_current);
        strcat(buffer, "/");
    }
    strcat(buffer, path);
    lfs_file_t *file = (lfs_file_t *)malloc(sizeof(lfs_file_t));
    if (lfs_file_open(&g_lfs_fs, file, buffer, flags) < 0) {
        free(file);
        return NULL;
    }
    return file;
}

lfs_ssize_t lfs_spiflash_write(lfs_file_t *file, const void *buffer, size_t size)
{
    return lfs_file_write(&g_lfs_fs, file, buffer, size);
}

lfs_ssize_t lfs_spiflash_read(lfs_file_t *file, void *buffer, size_t size)
{
    return lfs_file_read(&g_lfs_fs, file, buffer, size);
}

int lfs_spiflash_close(lfs_file_t *file)
{
    return lfs_file_close(&g_lfs_fs, file);
}
